package com.coolingme.springframework.aop.support;

import com.coolingme.springframework.aop.aspect.MeAdviceV1;
import com.coolingme.springframework.aop.aspect.MeAfterReturningAdviceInterceptor;
import com.coolingme.springframework.aop.aspect.MeAspectJAfterThrowingAdvice;
import com.coolingme.springframework.aop.aspect.MeMethodBeforeAdviceInterceptor;
import com.coolingme.springframework.aop.config.MeAopConfig;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * 工具类：解析AOP配置信息，并且构建切面于切点之前的关系
 *
 * @author wangyue
 * @date 2024/09/09 22:01
 */
@Slf4j
public class MeAdvisedSupport {

    public MeAdvisedSupport(MeAopConfig aopConfig) {
        this.aopConfig = aopConfig;
    }

    /**
     * AOP配置信息
     */
    @Getter
    private MeAopConfig aopConfig;

    /**
     * 目标对象实例
     */
    @Getter
    private Object target;

    /**
     * 目标对象的Class
     */
    @Getter
    private Class targetClass;

    /**
     * 切面表达式正则
     */
    private Pattern pointCutClassPattern;

    /**
     * 保存回调通知和目标切点之间的关键，一个方法对应多个回调通知
     * <p>
     * v1.0结构：目标方法 => (切面通知方法名 -> 切面通知)
     * v2.0结构：目标方法 => (切面通知的封装对象)
     */
//    private Map<Method, Map<String, MeAdviceV1>> methodCache;
    private Map<Method, List<Object>> methodCache;

    public void setTarget(Object target) {
        this.target = target;
    }

    public void setTargetClass(Class targetClass) {
        this.targetClass = targetClass;
        parse();
    }

    /**
     * @return 目标对象的Class是否能够匹配上切面正则
     */
    public boolean pointCutMath() {
        // this.targetClass.toString() =  class com.coolingme.demo.service.impl.HelloServiceImpl
        // pointCutClassPattern        =  class com\.coolingme\.demo\.service\..*ServiceImpl
        return pointCutClassPattern.matcher(this.targetClass.toString()).matches();
    }


    /**
     * 解析配置文件的方法
     */
    private void parse() {
        // 把Spring的Excpress变成Java能够识别的正则表达式
        // 切点      public .* com.coolingme.demo.service..*ServiceImpl..*(.*)
        // 切点正则  public .* com\.coolingme\.demo\.service\..*ServiceImpl\..*\(.*\)
        String pointCut = aopConfig.getPointCut()
                // public \.* com\.coolingme\.demo\.service\.\.*Service\.\.*(\.*)
                .replaceAll("\\.", "\\\\.")
                // public .* com\.coolingme\.demo\.service\..*Service\..*(.*)
                .replaceAll("\\\\.\\*", ".*")
                // public .* com\.coolingme\.demo\.service\..*Service\..*\(.*)
                .replaceAll("\\(", "\\\\(")
                // public .* com\.coolingme\.demo\.service\..*Service\..*\(.*\)
                .replaceAll("\\)", "\\\\)");


        // 保存专门匹配Class的正则 public .* com\.coolingme\.demo\.service\..*ServiceImpl
        String pointCutForClassRegex = pointCut.substring(0, pointCut.lastIndexOf("\\(") - 4);

        // 类的切点正则 class com\.coolingme\.demo\.service\..*ServiceImpl
        pointCutClassPattern = Pattern.compile("class " + pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf(" ") + 1));

        // 保存回调通知和目标切点之间的关键，一个方法对应多个回调通知
        /*
        add() => before()、after()
        remove() => before()、after()、afterThrowing()
         */

        //享元的共享池
//        methodCache = new HashMap<Method, Map<String, MeAdviceV1>>();
        methodCache = new HashMap<Method, List<Object>>();

        // 切点正则 public .* com\.coolingme\.demo\.service\..*ServiceImpl\..*\(.*\)
        Pattern pointCutPattern = Pattern.compile(pointCut);

        try {
            // 切面类的Class，class com.coolingme.demo.aspect.LogAspect，在instantionAopConfig中设置
            Class<?> aspectClass = Class.forName(this.aopConfig.getAspectClass());
            // methodName => Method
            Map<String, Method> aspectMethods = new HashMap<>();
            // 获取切面类的所有方法
            for (Method aspectMethod : aspectClass.getMethods()) {
                aspectMethods.put(aspectMethod.getName(), aspectMethod);
            }

            // 将目标类的所有方法 与切面的方法关联上
            for (Method targetMethod : this.targetClass.getMethods()) {
                // public java.lang.String com.coolingme.demo.service.impl.HelloServiceImpl.XXX(java.lang.String) throws java.lang.Exception
                String targetMethodStr = targetMethod.toString();

                // 先剪切掉throws XXXX
                if (targetMethodStr.contains("throws")) {
                    // public java.lang.String com.coolingme.demo.service.impl.HelloServiceImpl.XXX(java.lang.String)
                    targetMethodStr = targetMethodStr.substring(0, targetMethodStr.lastIndexOf("throws")).trim();
                }

                // 如果目标方法匹配切点表达式
                if (pointCutPattern.matcher(targetMethodStr).matches()) {
                    System.out.println("targetMethodStr = " + targetMethodStr);

                    // ---------------------------------------------v2------------------------------------------
                    List<Object> advices = new LinkedList<>();

                    if (!(null == aopConfig.getAspectBefore() || "".equals(aopConfig.getAspectBefore()))) {
                        advices.add(new MeMethodBeforeAdviceInterceptor(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectBefore())));
                    }
                    if (!(null == aopConfig.getAspectAfter() || "".equals(aopConfig.getAspectAfter()))) {
                        advices.add(new MeAfterReturningAdviceInterceptor(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectAfter())));
                    }
                    if (!(null == aopConfig.getAspectAfterThrow() || "".equals(aopConfig.getAspectAfterThrow()))) {
                        MeAspectJAfterThrowingAdvice advice = new MeAspectJAfterThrowingAdvice(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectAfterThrow()));
                        advice.setThrowName(aopConfig.getAspectAfterThrowingName());
                        advices.add(advice);
                    }
                    //跟目标代理类的业务方法和Advices建立一对多个关联关系，以便在Porxy类中获得
                    methodCache.put(targetMethod, advices);


                    // ---------------------------------------------v1------------------------------------------
                    /*Map<String, MeAdviceV1> advices = new HashMap<>();

                    if (!(null == aopConfig.getAspectBefore() || "".equals(aopConfig.getAspectBefore()))) {
                        advices.put(aopConfig.getAspectBefore(), new MeAdviceV1(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectBefore())));
                    }
                    if (!(null == aopConfig.getAspectAfter() || "".equals(aopConfig.getAspectAfter()))) {
                        advices.put(aopConfig.getAspectAfter(), new MeAdviceV1(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectAfter())));
                    }
                    if (!(null == aopConfig.getAspectAfterThrow() || "".equals(aopConfig.getAspectAfterThrow()))) {
                        advices.put(aopConfig.getAspectAfterThrow(), new MeAdviceV1(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectAfterThrow()), aopConfig.getAspectAfterThrowingName()));
                    }
                    // 跟目标代理类的业务方法和Advices建立一对多个关联关系，以便在Porxy类中获得
                    // 目标方法 => (切面通知方法名 -> 切面通知)
                    methodCache.put(targetMethod, advices);*/
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(String.format("---AOP start targetClass：%s，methodCache.size() = %s", this.targetClass.getSimpleName(), methodCache.size()));

    }


    /**
     * 根据一个代理类的方法，获得其对应的通知
     *
     * @param proxyMethod 代理类的方法对象
     * @param targetClass 目标类class
     * @return 对应的通知集合
     * @throws Exception
     */
    /*public Map<String, MeAdviceV1> getAdvices(Method proxyMethod, Class targetClass) throws Exception {
        // 享元设计模式的应用
        Map<String, MeAdviceV1> cache = methodCache.get(proxyMethod);
        // 缓存中获取不到代理类方法对象对应的通知
        if (null == cache) {
            // 根据代理类的方法对象，获取对应的目标类方法对象
            Method targetMethod = targetClass.getMethod(proxyMethod.getName(), proxyMethod.getParameterTypes());
            // 获取目标类方法对象对应的通知
            cache = methodCache.get(targetMethod);
            // 说明原本目标类中就没有代理此方法，如toString()
            if (null != cache) {
                // 将代理类方法对象对应的通知放入缓存中
                this.methodCache.put(proxyMethod, cache);
            }
        }
        return cache;
    }*/

    /**
     * 根据一个代理类的方法，获得其对应的通知
     * <p>
     * 同上，只是将缓存中获取到的通知集合，改为List<Object>
     *
     * @param proxyMethod 代理类的方法对象
     * @param targetClass 目标类class
     * @return 对应的通知集合
     * @throws Exception
     */
    public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method proxyMethod, Class<?> targetClass) throws Exception {
        // 享元设计模式的应用
        List<Object> cache = this.methodCache.get(proxyMethod);
        // 缓存中获取不到代理类方法对象对应的通知
        if (cache == null) {
            // 根据代理类的方法对象，获取对应的目标类方法对象
            Method targetMethod = targetClass.getMethod(proxyMethod.getName(), proxyMethod.getParameterTypes());
            // 获取目标类方法对象对应的通知
            cache = methodCache.get(targetMethod);
            // 说明原本目标类中就没有代理此方法，如toString()
            if (null != cache) {
                // 将代理类方法对象对应的通知放入缓存中
                this.methodCache.put(proxyMethod, cache);
            }
        }
        return cache;
    }
}
